// SPDX-License-Identifier: GPL-2.0
/* Copyright(c) 2022 - 2025 Mucse Corporation. */

#include <linux/debugfs.h>
#include <linux/module.h>

#include "rnpgbe.h"

#ifdef HAVE_RNP_DEBUG_FS
static struct dentry *rnpgbe_dbg_root;
static char rnpgbe_dbg_reg_ops_buf[256] = "";

/**
 * rnpgbe_dbg_reg_ops_read - read for reg_ops datum
 * @file: the opened file
 * @buffer: where to write the data for the user to read
 * @count: the size of the user's buffer
 * @ppos: file position offset
 **/
static ssize_t rnpgbe_dbg_reg_ops_read(struct file *file, char __user *buffer,
				       size_t count, loff_t *ppos)
{
	struct rnpgbe_adapter *adapter = file->private_data;
	char *buf;
	int len;

	/* don't allow partial reads */
	if (*ppos != 0)
		return 0;

	buf = kasprintf(GFP_KERNEL, "%s: %s\n", adapter->name,
			rnpgbe_dbg_reg_ops_buf);
	if (!buf)
		return -ENOMEM;

	if (count < strlen(buf)) {
		kfree(buf);
		return -ENOSPC;
	}

	len = simple_read_from_buffer(buffer, count, ppos, buf, strlen(buf));

	kfree(buf);
	return len;
}

/**
 * rnpgbe_dbg_reg_ops_write - write into reg_ops datum
 * @file: the opened file
 * @buffer: where to find the user's data
 * @count: the length of the user's data
 * @ppos: file position offset
 **/
static ssize_t rnpgbe_dbg_reg_ops_write(struct file *file,
					const char __user *buffer, size_t count,
					loff_t *ppos)
{
	struct rnpgbe_adapter *adapter = file->private_data;
	struct rnpgbe_hw *hw = &adapter->hw;
	int len;

	/* don't allow partial writes */
	if (*ppos != 0)
		return 0;
	if (count >= sizeof(rnpgbe_dbg_reg_ops_buf))
		return -ENOSPC;

	len = simple_write_to_buffer(rnpgbe_dbg_reg_ops_buf,
				     sizeof(rnpgbe_dbg_reg_ops_buf) - 1, ppos,
				     buffer, count);
	if (len < 0)
		return len;

	rnpgbe_dbg_reg_ops_buf[len] = '\0';

	if (strncmp(rnpgbe_dbg_reg_ops_buf, "write", 5) == 0) {
		u32 reg, value;
		int cnt;

		cnt = sscanf(&rnpgbe_dbg_reg_ops_buf[5], "%x %x", &reg, &value);
		if (cnt == 2) {
			if (reg >= 0x30000000) {
				rnpgbe_mbx_reg_write(hw, reg, value);
				e_dev_info("write: 0x%08x = 0x%08x\n", reg,
					   value);
			} else {
				rnpgbe_wr_reg(hw->hw_addr + reg, value);
				value = rnpgbe_rd_reg(hw->hw_addr + reg);
				e_dev_info("write: 0x%08x = 0x%08x\n", reg,
					   value);
			}
		} else {
			e_dev_info("write <reg> <value>\n");
		}
	} else if (strncmp(rnpgbe_dbg_reg_ops_buf, "read", 4) == 0) {
		u32 reg, value;
		int cnt;

		cnt = sscanf(&rnpgbe_dbg_reg_ops_buf[4], "%x", &reg);
		if (cnt == 1) {
			if (reg >= 0x30000000)
				value = rnpgbe_mbx_fw_reg_read(hw, reg);
			else
				value = rnpgbe_rd_reg(hw->hw_addr + reg);

			snprintf(rnpgbe_dbg_reg_ops_buf,
				 sizeof(rnpgbe_dbg_reg_ops_buf),
				 "0x%08x: 0x%08x", reg, value);
			e_dev_info("read 0x%08x = 0x%08x\n", reg, value);
		} else {
			e_dev_info("read <reg>\n");
		}
	} else {
		e_dev_info("Unknown command %s\n", rnpgbe_dbg_reg_ops_buf);
		e_dev_info("Available commands:\n");
		e_dev_info("   read <reg>\n");
		e_dev_info("   write <reg> <value>\n");
	}
	return count;
}

static const struct file_operations rnpgbe_dbg_reg_ops_fops = {
	.owner = THIS_MODULE,
	.open = simple_open,
	.read = rnpgbe_dbg_reg_ops_read,
	.write = rnpgbe_dbg_reg_ops_write,
};

static char rnpgbe_dbg_netdev_ops_buf[256] = "";

/**
 * rnpgbe_dbg_netdev_ops_read - read for netdev_ops datum
 * @file: the opened file
 * @buffer: where to write the data for the user to read
 * @count: the size of the user's buffer
 * @ppos: file position offset
 **/
static ssize_t rnpgbe_dbg_netdev_ops_read(struct file *file,
					  char __user *buffer, size_t count,
					  loff_t *ppos)
{
	struct rnpgbe_adapter *adapter = file->private_data;
	char *buf;
	int len;

	/* don't allow partial reads */
	if (*ppos != 0)
		return 0;

	buf = kasprintf(GFP_KERNEL, "%s: %s\n", adapter->name,
			rnpgbe_dbg_netdev_ops_buf);
	if (!buf)
		return -ENOMEM;

	if (count < strlen(buf)) {
		kfree(buf);
		return -ENOSPC;
	}

	len = simple_read_from_buffer(buffer, count, ppos, buf, strlen(buf));

	kfree(buf);
	return len;
}

/**
 * rnpgbe_dbg_netdev_ops_write - write into netdev_ops datum
 * @file: the opened file
 * @buffer: where to find the user's data
 * @count: the length of the user's data
 * @ppos: file position offset
 **/
static ssize_t rnpgbe_dbg_netdev_ops_write(struct file *file,
					   const char __user *buffer,
					   size_t count, loff_t *ppos)
{
	struct rnpgbe_adapter *adapter = file->private_data;
	int len;

	/* don't allow partial writes */
	if (*ppos != 0)
		return 0;
	if (count >= sizeof(rnpgbe_dbg_netdev_ops_buf))
		return -ENOSPC;

	len = simple_write_to_buffer(rnpgbe_dbg_netdev_ops_buf,
				     sizeof(rnpgbe_dbg_netdev_ops_buf) - 1,
				     ppos, buffer, count);
	if (len < 0)
		return len;

	rnpgbe_dbg_netdev_ops_buf[len] = '\0';

	if (strncmp(rnpgbe_dbg_netdev_ops_buf, "stat", 4) == 0) {
		rnpgbe_info("adapter->stat=0x%lx\n", adapter->state);
		rnpgbe_info("adapter->tx_timeout_count=%d\n",
			    adapter->tx_timeout_count);
	} else if (strncmp(rnpgbe_dbg_netdev_ops_buf, "tx_timeout", 10) == 0) {
#ifdef HAVE_NET_DEVICE_OPS
#ifdef HAVE_TX_TIMEOUT_TXQUEUE
		adapter->netdev->netdev_ops->ndo_tx_timeout(adapter->netdev,
							    UINT_MAX);
#else
		adapter->netdev->netdev_ops->ndo_tx_timeout(adapter->netdev);
#endif
#else
		adapter->netdev->tx_timeout(adapter->netdev);
#endif
		e_dev_info("tx_timeout called\n");
	} else {
		e_dev_info("Unknown command: %s\n", rnpgbe_dbg_netdev_ops_buf);
		e_dev_info("Available commands:\n");
		e_dev_info("    tx_timeout\n");
	}
	return count;
}

static const struct file_operations rnpgbe_dbg_netdev_ops_fops = {
	.owner = THIS_MODULE,
	.open = simple_open,
	.read = rnpgbe_dbg_netdev_ops_read,
	.write = rnpgbe_dbg_netdev_ops_write,
};

static void debugfs_command_help(struct device *dev, char *cmd_buf)
{
	dev_info(dev, "unknown or invalid command '%s'\n", cmd_buf);
	dev_info(dev, "available commands\n");
	dev_info(dev, "\t dump ring\n");
	dev_info(dev, "\t dump dma\n");
	dev_info(dev, "\t dump tx\n");
	dev_info(dev, "\t dump rx\n");
}

/**
 * rnpgbe_dbg_netdev_ops_write - write into netdev_ops datum
 * @file: the opened file
 * @buf: where to find the user's data
 * @count: the length of the user's data
 * @ppos: file position offset
 **/
static ssize_t rnpgbe_dbg_command_write(struct file *file,
					const char __user *buf,
					size_t count, loff_t *ppos)
{
	struct rnpgbe_adapter *adapter = file->private_data;
	struct device *dev = &adapter->pdev->dev;
	struct rnpgbe_hw *hw = &adapter->hw;
	char *cmd_buf, *cmd_buf_tmp;
	ssize_t ret;
	char **argv;
	int argc;

	/* don't allow partial writes */
	if (*ppos != 0)
		return 0;

	cmd_buf = memdup_user(buf, count + 1);
	if (IS_ERR(cmd_buf))
		return PTR_ERR(cmd_buf);
	cmd_buf[count] = '\0';

	cmd_buf_tmp = strchr(cmd_buf, '\n');
	if (cmd_buf_tmp) {
		*cmd_buf_tmp = '\0';
		count = (size_t)cmd_buf_tmp - (size_t)cmd_buf + 1;
	}

	argv = argv_split(GFP_KERNEL, cmd_buf, &argc);
	if (!argv) {
		ret = -ENOMEM;
		goto err_copy_from_user;
	}

	if (argc == 2 && !strncmp(argv[0], "dump", 4)) {
		ret = hw->ops.dump_debug_regs(hw, argv[1]);
		if (ret) {
			debugfs_command_help(dev, cmd_buf);
			ret = -EINVAL;
			goto command_write_error;
		}
	} else {
		debugfs_command_help(dev, cmd_buf);
		ret = -EINVAL;
		goto command_write_error;
	}

	/* if we get here, nothing went wrong; return bytes copied */
	ret = (ssize_t)count;

command_write_error:
	argv_free(argv);
err_copy_from_user:
	kfree(cmd_buf);

	/* This function always consumes all of the written input, or produces
	 * an error. Check and enforce this. Otherwise, the write operation
	 * won't complete properly.
	 */
	if (WARN_ON(ret != (ssize_t)count && ret >= 0))
		ret = -EIO;

	return ret;
}

static const struct file_operations rnpgbe_dbg_command_fops = {
	.owner = THIS_MODULE,
	.open = simple_open,
	.read = NULL,
	.write = rnpgbe_dbg_command_write,
};

/**
 * rnpgbe_dbg_netdev_temp_read - read temperature from hw
 * @file: the opened file
 * @buffer: where to find the user's data
 * @count: the length of the user's data
 * @ppos: file position offset
 **/
static ssize_t rnpgbe_dbg_netdev_temp_read(struct file *file,
					   char __user *buffer, size_t count,
					   loff_t *ppos)
{
	struct rnpgbe_adapter *adapter = file->private_data;
	struct rnpgbe_hw *hw = &adapter->hw;
	int temp = 0, voltage = 0;
	char *buf;
	int len;

	/* don't allow partial reads */
	if (*ppos != 0)
		return 0;

	temp = rnpgbe_mbx_get_temp(hw, &voltage);

	buf = kasprintf(GFP_KERNEL, "%s: temp: %d oC voltage:%d mV\n",
			adapter->name, temp, voltage);
	if (!buf)
		return -ENOMEM;

	if (count < strlen(buf)) {
		kfree(buf);
		return -ENOSPC;
	}

	len = simple_read_from_buffer(buffer, count, ppos, buf, strlen(buf));

	kfree(buf);
	return len;
}
static const struct file_operations rnpgbe_dbg_netdev_temp = {
	.owner = THIS_MODULE,
	.open = simple_open,
	.read = rnpgbe_dbg_netdev_temp_read,
};

/**
 * rnpgbe_dbg_adapter_init - setup the debugfs directory for the adapter
 * @adapter: the adapter that is starting up
 **/
void rnpgbe_dbg_adapter_init(struct rnpgbe_adapter *adapter)
{
	const char *name = pci_name(adapter->pdev);
	struct dentry *pfile;

	adapter->rnpgbe_dbg_adapter = debugfs_create_dir(name, rnpgbe_dbg_root);
	if (adapter->rnpgbe_dbg_adapter) {
		pfile = debugfs_create_file("reg_ops", 0600,
					    adapter->rnpgbe_dbg_adapter,
					    adapter, &rnpgbe_dbg_reg_ops_fops);
		if (!pfile)
			e_dev_err("debugfs reg_ops for %s failed\n", name);
		pfile = debugfs_create_file("netdev_ops", 0600,
					    adapter->rnpgbe_dbg_adapter,
					    adapter,
					    &rnpgbe_dbg_netdev_ops_fops);
		if (!pfile)
			e_dev_err("debugfs netdev_ops for %s failed\n", name);

		pfile = debugfs_create_file("temp", 0600,
					    adapter->rnpgbe_dbg_adapter,
					    adapter, &rnpgbe_dbg_netdev_temp);
		if (!pfile)
			e_dev_err("debugfs temp for %s failed\n", name);

		pfile = debugfs_create_file("command", 0600,
					    adapter->rnpgbe_dbg_adapter,
					    adapter, &rnpgbe_dbg_command_fops);
		if (!pfile)
			e_dev_err("debugfs temp for command failed\n");
	} else {
		e_dev_err("debugfs entry for %s failed\n", name);
	}
}

/**
 * rnpgbe_dbg_adapter_exit - clear out the adapter's debugfs entries
 * @adapter: the adapter that is starting up
 **/
void rnpgbe_dbg_adapter_exit(struct rnpgbe_adapter *adapter)
{
	debugfs_remove_recursive(adapter->rnpgbe_dbg_adapter);
	adapter->rnpgbe_dbg_adapter = NULL;
}

/**
 * rnpgbe_dbg_init - start up debugfs for the driver
 **/
void rnpgbe_dbg_init(void)
{
	rnpgbe_dbg_root = debugfs_create_dir(rnpgbe_driver_name, NULL);
	if (rnpgbe_dbg_root == NULL)
		pr_err("init of debugfs failed\n");
}

/**
 * rnpgbe_dbg_exit - clean out the driver's debugfs entries
 **/
void rnpgbe_dbg_exit(void)
{
	debugfs_remove_recursive(rnpgbe_dbg_root);
}
#endif
